package com.daz.gradle.annotationprocess;

import org.gradle.api.GradleException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.compile.JavaCompile;

import java.io.File;
import java.util.*;

public class PreCompileAnnotationProcessPlugin implements Plugin<Project> {

    public static final String CONFIGURATION_NAME = "preCompileAnnotationProcess";
    public static final String EXTENSION_NAME = "preCompileAnnotationProcess";
    private static final String DEFAULT_TARGET_PATH = Project.DEFAULT_BUILD_DIR_NAME + "/generated";

    @Override
    public void apply(Project project) {
        applyJavaPlugin(project);
        createConfiguration(project);
        PreCompileAnnotationProcessPluginExtension extension = createExtension(project);
        createPreCompileTask(project, extension);
    }


    private void applyJavaPlugin(Project project) {
        project.getPluginManager().apply(JavaPlugin.class);
    }

    private void createConfiguration(Project project) {
        Configuration configuration = project.getConfigurations().create(CONFIGURATION_NAME);
        configuration.extendsFrom(project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
    }

    private PreCompileAnnotationProcessPluginExtension createExtension(Project project) {
        return project.getExtensions().create(EXTENSION_NAME, PreCompileAnnotationProcessPluginExtension.class, project);
    }


    private void createPreCompileTask(Project project, PreCompileAnnotationProcessPluginExtension extension) {
        project.afterEvaluate(prj -> {
                    for (PreCompileAnnotationProcessDeclaration process : extension.getProcesses()) {
                        doCreateTask(prj, process);
                    }
                }
        );
    }

    private void doCreateTask(Project project, PreCompileAnnotationProcessDeclaration process) {
        String processName = process.getName();
        String taskName = "preCompile" + processName.substring(0, 1).toUpperCase() + processName.substring(1, processName.length());
        ConfigurationContainer configurations = project.getConfigurations();
        Configuration configuration = configurations.getByName(PreCompileAnnotationProcessPlugin.CONFIGURATION_NAME);
        JavaPluginConvention javaConvention = project.getConvention().findPlugin(JavaPluginConvention.class);
        assert javaConvention != null;
        checkProcessorDependency(configuration);
        File destFile = project.file(Optional.ofNullable(process.getDestinationDir()).orElse(DEFAULT_TARGET_PATH));
        Task generateTask = project.getTasks().create(taskName, JavaCompile.class, task -> {
            task.setDescription(task.getDescription());
            task.setGroup("build");
            task.source(javaConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getAllJava());
            task.setClasspath(configuration);
            List<String> compilerArgs = new ArrayList<>();
            compilerArgs.add("-proc:only");
            String processor = process.getProcessor();
            if (processor != null && processor.trim().length() > 0) {
                compilerArgs.add("-processor");
                compilerArgs.add(processor);
            }
            task.getOptions().setCompilerArgs(compilerArgs);
            task.setDestinationDir(destFile);
        });
        configSourceSet(javaConvention, destFile);
        configClassesTask(project, generateTask);
    }

    private void checkProcessorDependency(Configuration configuration) {
        if (configuration.getDependencies().size() < 1) {
            throw new GradleException("You must set the dependency of querydsl. \neg. \n" + "dependencies { \n querydslapt \"com.querydsl:querydsl-apt:4.2.1:jpa\" \n} \n ");
        }
    }

    private void configSourceSet(JavaPluginConvention javaConvention, File destFile) {
        javaConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).java(sourceSet -> {
            Set<File> dirs = new HashSet<>(sourceSet.getSrcDirs());
            dirs.add(destFile);
            sourceSet.setSrcDirs(dirs);
        });
    }

    private void configClassesTask(Project project, Task generateTask) {
        Task task = project.getTasks().findByName(JavaPlugin.COMPILE_JAVA_TASK_NAME);
        assert task != null;
        task.dependsOn(generateTask);
    }

}
